﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;

namespace Valhalla.Emulator
{
    public class GBCart
    {
        byte[] rom;
        
        bool gbc;
        public String GameName;
        public String CartType;
        public String Developer;

        int romSize;
        int romBanks;

        int ramSize;
        int ramBanks;

        public int licenseCode;
        bool japanese;
        int oldLicenseCode;
        int maskRomVersion;
        int checksum;
        int actualChecksum;
        int headerChecksum;
        int actualHeaderChecksum;
        bool noVerticalBlankInterruptHandler;
        bool noLCDCStatusInterruptHandler;
        bool noTimerOverflowInterruptHandler;
        bool noSerialTransferCompletionInterruptHandler;
        bool noHighToLowOfP10ToP13InterruptHandler;

        public bool Runable = false;
        public GBCart(string fname)
        {
            using (BinaryReader b = new BinaryReader(File.Open(fname, FileMode.Open)))
            {
                romSize = (int)b.BaseStream.Length;

                rom = new byte[romSize];
                b.Read(rom, 0, romSize);
            }

            if (rom[0x143] == 0x80)
                gbc = true;
            else
                gbc = false;

            setRomRamSizes();
            setOtherInfo();

            int makersCode = rom[0x14B];
            GameName = extractGameName();
            CartType = cartType();
            
            Developer= (makersCode == 0x33 ? ((char)rom[0x144]).ToString() + ((char)rom[0x145]).ToString() : makersCode.ToString("X2", System.Globalization.CultureInfo.InvariantCulture));
            Developer = DevelopersDictionary.Developers[Developer];
        }

        public byte ReadByte(int offset)
        {
            return rom[0x7FFF & offset]; //why 0x7FFF?
            /*
        A more detailed look at the memory regions is as follows:

        [0000-3FFF] Cartridge ROM, bank 0: The first 16,384 bytes of the cartridge program are always available at this 
          point in the memory map. Special circumstances apply:
        [0000-00FF] BIOS: When the CPU starts up, PC starts at 0000h, which is the start of the 256-byte GameBoy BIOS code. 
         * Once the BIOS has run, it is removed from the memory map, and this area of the cartridge rom becomes addressable.
        [0100-014F] Cartridge header: This section of the cartridge contains data about its name 
         * and manufacturer, and must be written in a specific format.
        [4000-7FFF] Cartridge ROM, other banks: Any subsequent 16k "banks" of the cartridge program can be made available 
         * to the CPU here, one by one; a chip on the cartridge is generally used to switch between banks, and make a particular
         * area accessible. The smallest programs are 32k, which means that no bank-selection chip is required.
            */
        }
        public override string ToString()
        {
            return "Game name: " + GameName + "\n"
                + "Cartrige type: " + CartType + "\n"
                + "Developer: " + Developer + "\n"
                + "GameBoy Color game: " + gbc.ToString() + "\n"
                + "Rom size: " + romSize + "\n"
                + "Rom banks: " + romBanks + "\n"
                + "Japanese: " + japanese + "\n"
                + "License code: " + licenseCode + "\n"
                + "Old licese code: " + oldLicenseCode + "\n"
                + "Mask rom version: " + maskRomVersion + "\n"
                + "Header checksum: " + headerChecksum + "\n"
                + "Accual header checksum: " + actualHeaderChecksum + "\n"
                + "Total checksum: " + checksum + "\n"
                + "noVerticalBlankInterruptHandler: " + noVerticalBlankInterruptHandler + "\n"
                + "noTimerOverflowInterruptHandler: " + noTimerOverflowInterruptHandler + "\n"
                + "noLCDCStatusInterruptHandler: " + noLCDCStatusInterruptHandler + "\n"
                + "noHighToLowOfP10ToP13InterruptHandler: " + noHighToLowOfP10ToP13InterruptHandler + "\n"
                + "noSerialTransferCompletionInterruptHandler: " + noSerialTransferCompletionInterruptHandler + "\n";
        }

        string cartType()
        {
            string type = "";
            switch (rom[0x147])
            {
                case 0x0:
                    { type = "ROM ONLY"; Runable = true; break; }
                case 0x1:
                    { type = "ROM+MBC1"; break; }
                case 0x2:
                    { type = "ROM+MBC1+RAM"; break; }
                case 0x3:
                    { type = "ROM+MBC1+RAM+BATTERY"; break; }
                case 0x5:
                    { type = "ROM+MBC2"; break; }
                case 0x6:
                    { type = "ROM+MBC2+BATTERY"; break; }
                case 0x8:
                    { type = "ROM+RAM"; break; }
                case 0x9:
                    { type = "ROM+RAM+BATTERY"; break; }
                case 0xB:
                    { type = "ROM+MMM01"; break; }
                case 0xC:
                    { type = "ROM+MMM01+SRAM"; break; }
                case 0xD:
                    { type = "ROM+MMM01+SRAM+BATT"; break; }
                case 0xF:
                    { type = "ROM+MBC3+TIMER+BATT"; break; }
                case 0x10:
                    { type = "ROM+MBC3+TIMER+RAM+BATT"; break; }
                case 0x11:
                    { type = "ROM+MBC3"; break; }
                case 0x12:
                    { type = "ROM+MBC3+RAM"; break; }
                case 0x13:
                    { type = "ROM+MBC3+RAM"; break; }
                case 0x19:
                    { type = "ROM+MBC5"; break; }
                case 0x1A:
                    { type = "ROM+MBC5+RAM"; break; }
                case 0x1B:
                    { type = "ROM+MBC5+RAM+BATT"; break; }
                case 0x1C:
                    { type = "ROM+MBC5+RUMBLE"; break; }
                case 0x1D:
                    { type = "ROM+MBC5+RUMBLE+SRAM"; break; }
                case 0x1E:
                    { type = "ROM+MBC5+RUMBLE+SRAM+BATT"; break; }
                default:
                    { type = "INVALID"; break; }
            }
            return type;
        }

        string extractGameName()
        {
            string gn = "";

            int i134 = 0x134;
            int i142 = 0x142;

            for (int i = i134; i <= i142; i++)
            {
                if (rom[i] != 0)
                    gn += Convert.ToChar(rom[i]);
            }

            if (gn == "")
                return "INVALID DATA";
            else return gn;       
        }

        void setRomRamSizes()
        {
            switch (rom[0x0148])
            {
                case 0x00:
                    romSize = 32 * 1024;
                    romBanks = 2;
                    break;
                case 0x01:
                    romSize = 64 * 1024;
                    romBanks = 4;
                    break;
                case 0x02:
                    romSize = 128 * 1024;
                    romBanks = 8;
                    break;
                case 0x03:
                    romSize = 256 * 1024;
                    romBanks = 16;
                    break;
                case 0x04:
                    romSize = 512 * 1024;
                    romBanks = 32;
                    break;
                case 0x05:
                    romSize = 1024 * 1024;
                    romBanks = 64;
                    break;
                case 0x06:
                    romSize = 2 * 1024 * 1024;
                    romBanks = 128;
                    break;
                case 0x52:
                    romSize = 1179648;
                    romBanks = 72;
                    break;
                case 0x53:
                    romSize = 1310720;
                    romBanks = 80;
                    break;
                case 0x54:
                    romSize = 1572864;
                    romBanks = 96;
                    break;
            }

            switch (rom[0x0149])
            {
                case 0x00:
                    ramSize = 0;
                    ramBanks = 0;
                    break;
                case 0x01:
                    ramSize = 2 * 1024;
                    ramBanks = 1;
                    break;
                case 0x02:
                    ramSize = 8 * 1024;
                    ramBanks = 1;
                    break;
                case 0x03:
                    ramSize = 32 * 1024;
                    ramBanks = 4;
                    break;
                case 0x04:
                    ramSize = 128 * 1024;
                    ramBanks = 16;
                    break;
            }
        }

        void setOtherInfo()
        {
            licenseCode = (((int)rom[0x0144]) << 4) | rom[0x0145];
            japanese = rom[0x014A] == 0x00;
            oldLicenseCode = rom[0x014B];
            maskRomVersion = rom[0x014C];

            headerChecksum = rom[0x014D];
            for (int i = 0x0134; i <= 0x014C; i++)
            {
                actualHeaderChecksum = actualHeaderChecksum - rom[i] - 1;
            }
            actualHeaderChecksum &= 0xFF;

            checksum = (((int)rom[0x014E]) << 8) | rom[0x014F];
            for (int i = 0; i < rom.Length; i++)
            {
                if (i != 0x014E && i != 0x014F)
                {
                    actualChecksum += rom[i];
                }
            }
            actualChecksum &= 0xFFFF;

            noVerticalBlankInterruptHandler = rom[0x0040] == 0xD9;
            noLCDCStatusInterruptHandler = rom[0x0048] == 0xD9;
            noTimerOverflowInterruptHandler = rom[0x0050] == 0xD9;
            noSerialTransferCompletionInterruptHandler = rom[0x0058] == 0xD9;
            noHighToLowOfP10ToP13InterruptHandler = rom[0x0060] == 0xD9;
        }
    }
}
